#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>

struct private_data {
    int gpio;
    int major;
    char *name;
};

static struct private_data g_private_data = {
    .name = "rk3588-led-gpio",
};

static int rk3588_led_gpio_open(struct inode *inode, struct file *file)
{
    file->private_data = &g_private_data;
    return 0;
}

static ssize_t rk3588_led_gpio_write(struct file *file, const char __user *buff, size_t len, loff_t *offset)
{
    struct private_data *driver = (struct private_data*)file->private_data;
    char *kbuff = len < KMALLOC_MAX_SIZE ? (char*)kzalloc(len, GFP_KERNEL) : (char*)vzalloc(len);
    unsigned long size_no_copy = copy_from_user(kbuff, buff, len);
    unsigned long data;
    int ret = kstrtoul(kbuff, 10, &data);
    if (ret == -ERANGE) {
        printk(KERN_ERR "[%s][%d]str %s out of range\n", __func__, __LINE__, kbuff);
    } else if (ret == -EINVAL) {
        printk(KERN_ERR "[%s][%d]str %s parsing error\n", __func__, __LINE__, kbuff);
    } else {
        int val = data ? 1 : 0;
        gpio_set_value(driver->gpio, val);
    }
    len < KMALLOC_MAX_SIZE ? kfree(kbuff) : vfree(kbuff);
    return len - size_no_copy;
}

static struct file_operations f_ops = {
    .open = rk3588_led_gpio_open,
    .write = rk3588_led_gpio_write,
};

static int rk3588_led_gpio_probe(struct platform_device *pdev)
{
    int ret;
    ret = register_chrdev(0, g_private_data.name, &f_ops);
    if (ret <= 0) {
        printk(KERN_ERR "[%s][%d]register char device error\n", __func__, __LINE__);
        goto REGISTER_CHRDEV_FAILED;
    }
    g_private_data.major = ret;
    printk("[%s][%d]major=%d\n", __func__, __LINE__, ret);

    ret = of_get_named_gpio(pdev->dev.of_node, "led-gpio", 0);
    if (ret < 0) {
        printk(KERN_ERR "[%s][%d]get led-gpio from devicetree failed.ret=%d\n", __func__, __LINE__, ret);
        goto OF_FAILED;
    }
    g_private_data.gpio = ret;

    ret = gpio_request(g_private_data.gpio, "led-gpio");
    if (ret) {
        printk(KERN_ERR "[%s][%d]gpio_request failed\n", __func__, __LINE__);
        goto GPIO_REQUST_FAILED;
    }

    ret = gpio_direction_output(g_private_data.gpio, 0);
    if (ret) {
        printk(KERN_ERR "[%s][%d]gpio_direction_output failed\n", __func__, __LINE__);
        goto GPIO_DIRECTION_FAILED;
    }

    return 0;
GPIO_DIRECTION_FAILED:
    gpio_free(g_private_data.gpio);
GPIO_REQUST_FAILED:
OF_FAILED:
    unregister_chrdev(g_private_data.major, g_private_data.name);
REGISTER_CHRDEV_FAILED:
    return ret;
}

static int rk3588_led_gpio_remove(struct platform_device *pdev)
{
    gpio_free(g_private_data.gpio);
    unregister_chrdev(g_private_data.major, g_private_data.name);
    return 0;
}

static struct of_device_id match_table[] = {
    {.compatible = "rk3588-led-gpio,guoerba"},
};

static struct platform_driver rk3588_led_gpio_driver = {
    .probe = rk3588_led_gpio_probe,
    .remove = rk3588_led_gpio_remove,
    .driver = {
        .name = "rk3588-led-gpio-driver",
        .of_match_table = match_table,
    }
};

static int __init rk3588_led_gpio_init(void)
{
    if (platform_driver_register(&rk3588_led_gpio_driver)) {
        printk("[%s][%d]register platform driver failed\n", __func__, __LINE__);
        return -EBUSY;
    }
    return 0;
}

static void __exit rk3588_led_gpio_exit(void)
{
    platform_driver_unregister(&rk3588_led_gpio_driver);
}

module_init(rk3588_led_gpio_init);
module_exit(rk3588_led_gpio_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yujie Guo");
MODULE_DESCRIPTION("rk3588-led-gpio driver for Learning");



